#include "swap.h"
#include "threads/vaddr.h"
#include "threads/synch.h"
#include "threads/thread.h"
#include "threads/palloc.h"
#include "filesys/filesys.h"
#include "userprog/pagedir.h"
#include "devices/block.h"


struct block *swap_device;	/* represents the swap device */
static struct bitmap *swap_bitmap; /* represents the swap device bitmap*/
struct lock swap_syslock;		/* used for synchronization over swap device */

void
initialize_swap (void)
{
  swap_device = block_get_role (BLOCK_SWAP);
  swap_bitmap = bitmap_create (block_size (swap_device));
  lock_init (&swap_syslock);
}

void
free_swap (uint32_t *pte)
{
  struct page_struct *ps = supp_page_table_lookup (pte);
  if (ps && ps->frame->pos_flag == FRAME_ON_SWAP)
  {
    lock_acquire (&swap_syslock);
    bitmap_set_multiple (swap_bitmap, ps->frame->sector_num, 
              		 PGSIZE / BLOCK_SECTOR_SIZE, false);
    lock_release (&swap_syslock);
  }
}

/* 
  Moves the input FRAME into memory. If no space is available, then evicts
  one of the in-memory frame and install the input FRAME at its place.

  Returns true once the FRAME is moved in memory.
  Returns false, only if no frame can be evicted.
*/
bool
swap_in (struct frame_struct *frame)
{
  ASSERT (lock_held_by_current_thread (&frame->frame_lock));
  
  struct block *block_device;
  size_t f_length= frame->len;
  block_sector_t sector_num = frame->sector_num;

  /* Get a page of memory. */
  uint8_t *kpage = palloc_get_page (PAL_USER | PAL_ZERO);

  if (!kpage)
  {
    kpage = evict_frame ();
    if (!kpage)
      return false;
  }

  if (frame->is_all_zeroes)
  {
    memset (kpage, 0, PGSIZE);
    goto done;
  }

  /* If Frame is on disk then retrive it */
  if (frame->pos_flag == FRAME_ON_DISK)
  {
    block_device = fs_device;
    lock_acquire (&global_filesys_lock);
  }

  else if (frame->pos_flag == FRAME_ON_SWAP)
  {
    block_device = swap_device;
    lock_acquire (&global_swapsys_lock); 
  }
   
  block_sector_t bst;
  for (bst = 0; bst < PGSIZE / BLOCK_SECTOR_SIZE; bst++)
    block_read (block_device, sector_num + bst, kpage + BLOCK_SECTOR_SIZE * bst);

  if ((f_length < PGSIZE) && (block_device == fs_device))
    memset (kpage + f_length, 0, PGSIZE - f_length);

  if (block_device == fs_device)
    lock_release (&global_filesys_lock);
  else 
  {
    lock_release (&global_swapsys_lock);
    lock_acquire (&swap_syslock);
    /* Free the swap slot */
    bitmap_set_multiple (swap_bitmap, sector_num,
				   PGSIZE / BLOCK_SECTOR_SIZE, false);
    lock_release (&swap_syslock);
  }
  
  done:
  frame->vaddr = kpage;
  frame->pos_flag = FRAME_IN_MEM;
  return true;
}

/* Moves the input frame from memory to either on disk/swap depending
   based on the property of the FRAME. 
   Clears the page associated with the frame and also reset its dirty bit.
*/
bool
swap_out (struct frame_struct *frame)
{

  ASSERT (lock_held_by_current_thread (&frame->frame_lock));

  struct block *block_device;
  block_sector_t sector_num = 0;
  int type = frame->type_flag;
  int position;
 
  uint8_t *kpage = frame->vaddr;
  if (!kpage)
    return false;
  
  bool dirty = pagedir_is_dirty (frame->t->pagedir, kpage);

  if (frame->is_all_zeroes && dirty)
    frame->is_all_zeroes = false;

  if ((type == PROCESS_STACK)
      || (type == EXECUTABLE_FILE && dirty))
  {
    block_device = swap_device;
    lock_acquire (&swap_syslock);
    sector_num = bitmap_scan_and_flip (swap_bitmap, 0,
                                       PGSIZE / BLOCK_SECTOR_SIZE, false);
    lock_release (&swap_syslock);
    position = FRAME_ON_SWAP;
  }

  else if (type == MMAP_FILE && dirty)
  {
    block_device = fs_device;
    sector_num = frame->sector_num;
    position = FRAME_ON_DISK;
  } 

  else
  {
    sector_num = frame->sector_num;
    position = FRAME_ON_DISK;
    goto done;
  }

  if (block_device == fs_device)
    lock_acquire (&global_filesys_lock);
  else 
    lock_acquire (&global_swapsys_lock); 

  int b;
  for (b = 0; b < PGSIZE / BLOCK_SECTOR_SIZE; b++)
    block_write (block_device, sector_num + b, kpage + BLOCK_SECTOR_SIZE * b); 

  if (block_device == fs_device)
    lock_release (&global_filesys_lock);
  else 
    lock_release (&global_swapsys_lock); 

  done:
  pagedir_set_dirty (frame->t->pagedir, kpage, false);
  pagedir_set_accessed (frame->t->pagedir, kpage, false);
  frame->vaddr = NULL;
  frame->sector_num = sector_num;
  frame->pos_flag = position;
  pagedir_clear_page (frame->t->pagedir, frame->upage);
  lock_release (&frame->frame_lock);
  return true;
}
